#include "StdAfx.h"
#include "M2Manager.h"

VertexCache* Singleton<VertexCache>::m_instance = 0;

bool ADTChunk::m_initEffects = true;
ID3DXEffect* ADTChunk::m_blendEffect[4];
float ADTChunk::m_texcoords[145][2];
float ADTChunk::m_alphaCoords[145][2];
ui16 ADTChunk::triangleIndices[64][12];
IDirect3DTexture9* ADTChunk::m_sIdTexture = 0;
#define HOLELEN (CHUNKSIZE / 4)

static Texture* gLockTex;

Vertex3FRHWTU gLockVert[4] = 
{
	{ 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f },
	{ 32.0f, 0.0f, 0.0f, 1.0f, 1.0f, 0.0f },
	{ 32.0f, 32.0f, 0.0f, 1.0f, 1.0f, 1.0f },
	{ 0.0f, 32.0f, 0.0f, 1.0f, 0.0f, 1.0f },
};

ui32 holeOffsets[4][4] =
{
	{ 1 << 0, 1 << 1, 1 << 2, 1 << 3 },
	{ 1 << 4, 1 << 5, 1 << 6, 1 << 7 },
	{ 1 << 8, 1 << 9, 1 << 10, 1 << 11 },
	{ 1 << 12, 1 << 13, 1 << 14, 1 << 15 },
};

int indexMapBuf(int x, int y)
{
	return ((y + 1) / 2) * 9 + (y / 2) * 8 + x;
}

ADTChunk::ADTChunk(ui32 offset, CMpqFile* pFile, ADTModel* pParent, FileBuffer* buffer, bool initial) : m_baseOffset(offset), m_pFile(pFile),
	m_pParent(pParent), m_parentBuffer(buffer), m_holeTexture(0), mChangeNormals(false), mLastNormalChange(0), mRenderer(this)
{
	m_loaded = false;
	if(!pFile || !pParent || !offset)
		return;
	for(ui32 i = 0; i < 3; ++i)
	{
		m_alphaTex[i] = 0;
		m_blendTexture[i] = 0;
	}

	m_texture = 0;
	m_shadowTex = 0;

	m_locked = false;
	m_parentBuffer->Read(m_baseOffset + 4, 4, &m_chunkSize);
	m_parentBuffer->Read(m_baseOffset + 8, sizeof(MCNKHDR), &m_header);

	xbase = m_header.chunkpos[1] * -1.0f + ZEROPOINT;
	ybase = m_header.chunkpos[2];
	zbase = m_header.chunkpos[0] * -1.0f + ZEROPOINT;

	//if(initial)
	//	Load();


	if(m_initEffects)
		InitStatics();

	LoadVertices();

	sLoading.TileLoaded();
	m_lastRender = GetTickCount();
}

void ADTChunk::Draw(Vertex3F position, Vertex3F tar)
{
	BYTE keys[256];
	GetKeyboardState(keys);

	double dx = xbase - position.x;
	double dz = zbase - position.z;
	double dist = dx * dx + dz * dz;
	double fdist = fogDistance + 0.16f * TILESIZE;
	if(dist > fdist * fdist)
		return;

	Vertex3F front(tar.x - position.x, tar.y - position.y, tar.z - position.z);
	Vertex3F target((xbase + CHUNKSIZE / 2) - position.x, ybase - position.y, (zbase + CHUNKSIZE / 2) - position.z);
	Vertex3F target2D((xbase + CHUNKSIZE / 2) - position.x, 0.0f, (zbase + CHUNKSIZE / 2) - position.z);
	
	float angle = acos((front.x * target.x + front.z * target.z + front.y * target.y) / (front.Length() * target.Length()));
	
	if((angle * 180) / M_PI > 60 && target2D.Length() > 3 * CHUNKSIZE)
		return;

	m_renderLock.Acquire();

	if(!m_loaded)
		Load();

	if(!m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).isFinished())
		return;

	mRenderer.render();

	m_renderLock.Release();
	return;

	if(m_locked)
	{
		D3DXMATRIX matWorld, matView, matProj;
		sD3D->GetTransform(D3DTS_PROJECTION, &matProj);
		sD3D->GetTransform(D3DTS_VIEW, &matView);
		D3DXVECTOR3 tmp, out;
		tmp.x = xbase + CHUNKSIZE / 2;
		tmp.z = zbase + CHUNKSIZE / 2;
		tmp.y = sD3D.GetLandHeight(tmp.x, tmp.z);
		D3DVIEWPORT9 vPort;
		sD3D->GetViewport(&vPort);
		D3DXVec3Project(&out, &tmp, &vPort, &matProj, &matView, &matID);
		out.x -= 16;
		out.y -= 16;
		Vertex3FRHWTU LockVert[4] = { 0 };
		for(ui32 i = 0; i < 4; ++i)
		{
			LockVert[i].x = gLockVert[i].x + out.x;
			LockVert[i].y = gLockVert[i].y + out.y;
			LockVert[i].u = gLockVert[i].u;
			LockVert[i].v = gLockVert[i].v;
			LockVert[i].z = gLockVert[i].z;
			LockVert[i].rhw = gLockVert[i].rhw;
		}
		sD3D->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE);
		sD3D->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
		sD3D->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
		sD3D->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE);
		sD3D->SetFVF(VERTEX3FRHWT);
		sD3D->SetTexture(0, *gLockTex);
		sD3D->DrawPrimitiveUP(D3DPT_TRIANGLEFAN, 2, LockVert, sizeof(Vertex3FRHWTU));
		sD3D->SetRenderState(D3DRS_ALPHABLENDENABLE, FALSE);
	}
	m_renderLock.Release();
}

void ADTChunk::Deinit()	
{
	m_loaded = false;
	m_renderLock.Acquire();
	for(ui32 i = 0; i < 3; ++i)
	{
		if(m_alphaTex[i])
		{
			while(m_alphaTex[i]->Release());
			m_alphaTex[i] = 0;
		}
	}

	for(ui32 i = 1; i < m_header.nLayers; ++i)
	{
		if(m_blendTexture[i - 1])
			m_blendTexture[i - 1]->DecRef();
	}
	if(m_texture)
		m_texture->DecRef();
	while(m_shadowTex && m_shadowTex->Release());
	while(m_holeTexture && m_holeTexture->Release());
	m_renderLock.Release();

}

void ADTChunk::ChangeHeight(Vertex3F midPoint, float radius, float amount, ui32 type)
{
	amount /= 30;
	float dist, xdiff, zdiff;

	bool changed = false;

	xdiff = xbase - midPoint.x + CHUNKSIZE / 2;
	zdiff = zbase - midPoint.z + CHUNKSIZE / 2;

	dist = sqrt(xdiff * xdiff + zdiff * zdiff);

	if(dist > (radius + MAPCHUNK_RADIUS))
		return;

	for(ui32 i = 0; i < 145; ++i)
	{
		Vertex3FCNT2& v = rlVertices[i];
		xdiff = v.x - midPoint.x;
		zdiff = v.z - midPoint.z;

		dist = sqrt(xdiff * xdiff + zdiff * zdiff);

		if(dist > radius)
			continue;
		switch(type)
		{
		case 0:
			v.y += amount;
			break;
		case 1:
			v.y += amount * (1.0f - dist / radius);
			break;
		case 2:
			v.y += amount / (1.0f + dist / radius);
			break;
		case 3:
			v.y += (((0.0f - amount) / powf(radius, 2.0f)) * (powf(dist, 2.0f))) + amount;
			break;
		}
		changed = true;
	}

	if(changed)
	{
		ReloadNormals();
		sD3D.AddChanged(m_pParent);
		void* pLock;
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Lock(&pLock);
		memcpy(pLock, rlVertices, 145 * Vertex3FCNT2::stride);
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Unlock();
	}
}

void ADTChunk::FlattenHeight(Vertex3F midPoint, float radius, float amount, ui32 type)
{
	float dist, xdiff, zdiff;

	bool changed = false;

	xdiff = xbase - midPoint.x + CHUNKSIZE / 2;
	zdiff = zbase - midPoint.z + CHUNKSIZE / 2;

	dist = sqrt(xdiff * xdiff + zdiff * zdiff);

	if(dist > (radius + MAPCHUNK_RADIUS))
		return;

	for(ui32 i = 0; i < 145; ++i)
	{
		Vertex3FCNT2& v = rlVertices[i];
		xdiff = v.x - midPoint.x;
		zdiff = v.z - midPoint.z;

		dist = sqrt(xdiff * xdiff + zdiff * zdiff);

		if(dist > radius)
			continue;
		switch(type)
		{
		case 3:
		case 0:
			v.y = amount * v.y + (1 - amount) * midPoint.y;
			break;
		case 1:
			{
				float nremain = 1 - (1 - amount) * (1 - dist / radius);
				v.y = nremain * v.y + (1.0f - nremain) * midPoint.y;
				break;
			}
		case 2:
			{
				float nremain = 1.0f - pow(1.0f - amount, (1.0f + dist / radius));
				v.y = nremain * v.y + (1.0f - nremain) * midPoint.y;
				break;
			}
		}
		changed = true;
	}

	if(changed)
	{
		ReloadNormals();
		sD3D.AddChanged(m_pParent);
		void* pLock;
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Lock(&pLock);
		memcpy(pLock, rlVertices, 145 * Vertex3FCNT2::stride);
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Unlock();
	}
}

void ADTChunk::Save()
{
	if(!m_loaded)
		return;
	float heights[145];
	for(ui32 i = 0; i < 145; ++i)
	{
		heights[i] = rlVertices[i].y - ybase;
	}
	m_parentBuffer->Write(m_baseOffset + m_header.ofsHeights + 0x8, 145 * 4, heights);
	m_parentBuffer->Write(m_baseOffset + 0x8, sizeof(MCNKHDR), &m_header);
	for(ui32 i = 0; i < m_textureLayers.size(); ++i)
	{
		TileTexLayer& l = m_textureLayers[i];
		m_parentBuffer->Write(m_baseOffset + 0x8 + m_header.ofsLayers + i * sizeof(TileTexLayer), sizeof(TileTexLayer), &l);
	}

	if(m_header.flags & 0x01)
	{
		ui8 shadow[512];
		ui32 counter = 0;
		for(ui32 i = 0; i < 64; ++i)
		{
			ui8 curBuff[8] = { 0 };
			ui8* ptr = curBuff;
			for(ui32 j = 0; j < 8; ++j, ++ptr)
			{
				for(ui32 b = 0; b < 8; ++b)
				{
					if(m_shadowMap[counter++][3])
						*ptr |= (1 << b);
				}
			}
			memcpy(shadow + (i * 8), curBuff, 8);
		}
		m_parentBuffer->Write(m_baseOffset + m_header.ofsShadow + 0x08, 512, shadow);
	}

	std::string szFileName = m_pParent->GetFileName();
	std::string::size_type t1 = szFileName.rfind('\\');
	std::string::size_type t2 = szFileName.find('_', t1);
	std::string sMap = szFileName.substr(t1 + 1, (t2 - t1 - 1));
	std::string sWDT = "World\\Maps\\";
	sWDT += sMap + "\\" + sMap + ".wdt";
	CMpqFile fil(sWDT);
	WDTHeader hdr;
	fil.Read(0x14, sizeof(WDTHeader), &hdr);

	if(m_header.nLayers > 1)
	{
		if(hdr.flags & 0x4)
		{
			ui8* alpha = new ui8[4096 * (m_header.nLayers - 1)];
			for(ui32 j = 0; j < (m_header.nLayers - 1); ++j)
			{
				for(ui32 k = 0; k < 4096; ++k)
				{
					alpha[j * 4096 + k] = m_alpha[k][j];
				}
			}
			m_parentBuffer->Write(m_baseOffset + 0x8 + m_header.ofsAlpha, 4096 * (m_header.nLayers - 1), alpha);
			delete [] alpha;
		}
		else
		{
			ui8* alpha = new ui8[32 * 64 * (m_header.nLayers - 1)];
			for(ui32 j = 0; j < (m_header.nLayers - 1); ++j)
			{
				ui8* ptr = alpha + (j * 32 * 64);
				for(ui32 i = 0; i < 2048; ++i)
				{
					ui8 lowerNibble = (ui8)max(min(((float)m_alpha[i * 2][j]) / 255.0f * 15.0f + 0.5f, 15.0f), 0.0f);
					ui8 upperNibble = (ui8)max(min(((float)m_alpha[i * 2 + 1][j]) / 255.0f * 15.0f + 0.5f , 15.0f), 0.0f);
					*ptr++ = (upperNibble << 4) + lowerNibble;
				}
			}
			m_parentBuffer->Write(m_baseOffset + 0x8 + m_header.ofsAlpha, 32 * 64 * (m_header.nLayers - 1), alpha);
			delete [] alpha;
		}
	}
	m_parentBuffer->Write(m_baseOffset + 0x4, 4, &m_chunkSize);
}

std::string ADTChunk::GetTextureName(ui32 index)
{
	try
	{
		TileTexLayer& tl = GetTexLayer(index);
		return m_pParent->GetTexture(tl.texOffset);
	}
	catch(...)
	{
		return "";
	}
}

void ADTChunk::AddTextureLayerNew(std::string szTexture)
{
	TileTexLayer tl;
	tl.texOffset = m_pParent->AddTexture(szTexture);
	tl.ofsAlpha = (m_header.nLayers - 2) * 4096;
	tl.flags = 0x100;
	tl.effectId = 0;
	m_textureLayers.push_back(tl);
	m_parentBuffer->Append(m_baseOffset + m_header.ofsLayers + 0x8 + (m_header.nLayers - 1) * sizeof(TileTexLayer), sizeof(TileTexLayer), &tl);
	RecalcOffsets(m_baseOffset + m_header.ofsLayers + 0x8 + (m_header.nLayers - 1) * sizeof(TileTexLayer), sizeof(TileTexLayer));
	m_pParent->RecalcOffsets(m_baseOffset + m_header.ofsLayers + 0x8 + (m_header.nLayers - 1) * sizeof(TileTexLayer), sizeof(TileTexLayer));
	m_chunkSize += sizeof(TileTexLayer);
	m_header.sizeAlpha += 4096;
	ui32 alSz = 4096 * (m_header.nLayers - 1);
	m_parentBuffer->Write(m_baseOffset +  m_header.ofsAlpha + 0x4, 4, &alSz);
	ui8 dummy[4096] = { 0 };
	m_parentBuffer->Append(m_baseOffset + m_header.ofsAlpha + 0x8 + (m_header.nLayers - 2) * 4096,
		4096, dummy);
	RecalcOffsets(m_baseOffset + m_header.ofsAlpha + 0x8 + (m_header.nLayers - 2) * 4096, 4096);
	m_pParent->RecalcOffsets(m_baseOffset + m_header.ofsAlpha + 0x8 + (m_header.nLayers - 2) * 4096, 4096);
	m_chunkSize += 4096;
	m_pParent->UpdateSizes(4096 + sizeof(TileTexLayer), m_baseOffset);
	ui32 nSize = m_header.nLayers * sizeof(TileTexLayer);
	m_parentBuffer->Write(m_baseOffset + m_header.ofsLayers + 0x04, 4, &nSize);
}

void ADTChunk::AddTextureLayer(std::string szTexture)
{
	std::string szFileName = m_pParent->GetFileName();
	std::string::size_type t1 = szFileName.rfind('\\');
	std::string::size_type t2 = szFileName.find('_', t1);
	std::string sMap = szFileName.substr(t1 + 1, (t2 - t1 - 1));
	std::string sWDT = "World\\Maps\\";
	sWDT += sMap + "\\" + sMap + ".wdt";
	CMpqFile fil(sWDT);
	WDTHeader hdr;
	fil.Read(0x14, sizeof(WDTHeader), &hdr);

	if(hdr.flags & 0x4)
	{
		AddTextureLayerNew(szTexture);
		return;
	}
	TileTexLayer tl;
	tl.texOffset = m_pParent->AddTexture(szTexture);
	tl.ofsAlpha = (m_header.nLayers - 2) * 2048;
	tl.flags = 0x100;
	tl.effectId = 0;
	m_textureLayers.push_back(tl);
	m_parentBuffer->Append(m_baseOffset + m_header.ofsLayers + 0x8 + (m_header.nLayers - 1) * sizeof(TileTexLayer), sizeof(TileTexLayer), &tl);
	RecalcOffsets(m_baseOffset + m_header.ofsLayers + 0x8 + (m_header.nLayers - 1) * sizeof(TileTexLayer), sizeof(TileTexLayer));
	m_pParent->RecalcOffsets(m_baseOffset + m_header.ofsLayers + 0x8 + (m_header.nLayers - 1) * sizeof(TileTexLayer), sizeof(TileTexLayer));
	m_chunkSize += sizeof(TileTexLayer);
	m_header.sizeAlpha += 2048;
	ui32 alSz = 2048 * (m_header.nLayers - 1);
	m_parentBuffer->Write(m_baseOffset +  m_header.ofsAlpha + 0x4, 4, &alSz);
	ui8 dummy[2048] = { 0 };
	m_parentBuffer->Append(m_baseOffset + m_header.ofsAlpha + 0x8 + (m_header.nLayers - 2) * 2048,
		2048, dummy);
	RecalcOffsets(m_baseOffset + m_header.ofsAlpha + 0x8 + (m_header.nLayers - 2) * 2048, 2048);
	m_pParent->RecalcOffsets(m_baseOffset + m_header.ofsAlpha + 0x8 + (m_header.nLayers - 2) * 2048, 2048);
	m_chunkSize += 2048;
	m_pParent->UpdateSizes(2048 + sizeof(TileTexLayer), m_baseOffset);
	ui32 nSize = m_header.nLayers * sizeof(TileTexLayer);
	m_parentBuffer->Write(m_baseOffset + m_header.ofsLayers + 0x04, 4, &nSize);
}

i32 ADTChunk::AddTexture(ui32 teLevel, Texture* pTexture, ui32 flags)
{
	for(ui32 i = 0; i < m_header.nLayers - 1; ++i)
	{
		if(mRenderer.getTexture(i + 1)->isEqual(pTexture))
			return i + 1;
	}

	i32 texLevel = -1;

	switch(flags)
	{
	case 2:
		{
			if(m_header.nLayers > 1)
			{
				if(m_locked)
					return -1;
				mRenderer.getTexture(1)->DecRef();
				mRenderer.setTexture(1, pTexture);
				m_textureLayers[1].texOffset = m_pParent->AddTexture(pTexture->GetName());
				for(ui32 i = 0; i < 4096; ++i)
				{
					m_alphaf[i][0] = 0.0f;
					m_alpha[i][0] = 0;
				}
				pTexture->AddRef();
				return 1;
			}
			else
			{
				mRenderer.setTexture(1, pTexture);
				pTexture->AddRef();
				for(ui32 i = 0; i < 4096; ++i)
				{
					m_alphaf[i][0] = 0.0f;
					m_alpha[i][0] = 0;
				}
				++m_header.nLayers;
				AddTextureLayer(pTexture->GetName());
				return 1;
			}
			break;
		}
	case 4:
		{
			if(m_header.nLayers > 2)
			{
				if(m_locked)
					return -1;
				mRenderer.getTexture(2)->DecRef();
				mRenderer.setTexture(2, pTexture);
				m_textureLayers[2].texOffset = m_pParent->AddTexture(pTexture->GetName());
				for(ui32 i = 0; i < 4096; ++i)
				{
					m_alphaf[i][1] = 0.0f;
					m_alpha[i][1] = 0;
				}
				pTexture->AddRef();
				return 2;
			}
			else
			{
				i32 ret = 0;
				if(m_header.nLayers == 1)
				{
					mRenderer.setTexture(1, pTexture);
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][0] = 0.0f;
						m_alpha[i][0] = 0;
					}
					++m_header.nLayers;
					ret = 1;
				}
				else
				{
					mRenderer.setTexture(2, pTexture);
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][1] = 0.0f;
						m_alpha[i][1] = 0;
					}
					++m_header.nLayers;
					ret = 2;
				}
				AddTextureLayer(pTexture->GetName());
				return ret;
			}
			break;
		}
	case 8:
		{
			if(m_header.nLayers > 3)
			{
				if(m_locked)
					return -1;
				mRenderer.setTexture(3, pTexture);
				m_textureLayers[3].texOffset = m_pParent->AddTexture(pTexture->GetName());
				pTexture->AddRef();
				for(ui32 i = 0; i < 4096; ++i)
				{
					m_alphaf[i][2] = 0.0f;
					m_alpha[i][2] = 0;
				}
				return 3;
			}
			else
			{
				switch(m_header.nLayers)
				{
				case 1:
					{
						mRenderer.setTexture(1, pTexture);
						pTexture->AddRef();
						for(ui32 i = 0; i < 4096; ++i)
						{
							m_alphaf[i][0] = 0.0f;
							m_alpha[i][0] = 0;
						}
						++m_header.nLayers;
						return 1;
					}
				case 2:
					{
						mRenderer.setTexture(2, pTexture);
						pTexture->AddRef();
						for(ui32 i = 0; i < 4096; ++i)
						{
							m_alphaf[i][1] = 0.0f;
							m_alpha[i][1] = 0;
						}
						++m_header.nLayers;
						return 2;
					}
				case 3:
					{
						mRenderer.setTexture(3, pTexture);
						pTexture->AddRef();
						for(ui32 i = 0; i < 4096; ++i)
						{
							m_alphaf[i][2] = 0.0f;
							m_alpha[i][2] = 0;
						}
						++m_header.nLayers;
						return 3;
					}
				}
				AddTextureLayer(pTexture->GetName());
			}
		}
	case 16:
		{
			switch(m_header.nLayers)
			{
			case 1:
			case 2:
			case 3:
				{
					mRenderer.setTexture(m_header.nLayers, pTexture);
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][m_header.nLayers - 1] = 0.0f;
						m_alpha[i][m_header.nLayers - 1] = 0;
					}
					i32 ret = m_header.nLayers++;
					mRenderer.textureAdded();
					AddTextureLayer(pTexture->GetName());
					return ret;
				}
			default:
				return -1;
			}
		}
	case 16 | 1:
		{
			switch(m_header.nLayers)
			{
			case 1:
			case 2:
			case 3:
				{
					mRenderer.setTexture(m_header.nLayers, pTexture);
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][m_header.nLayers - 1] = 0.0f;
						m_alpha[i][m_header.nLayers - 1] = 0;
					}
					i32 ret = m_header.nLayers++;
					AddTextureLayer(pTexture->GetName());
					mRenderer.textureAdded();
					return ret;
				}
			default:
				{
					return -1;
				}
			}
		}
	case 16 | 2:
		{
			switch(m_header.nLayers)
			{
			case 1:
			case 2:
			case 3:
				{
					mRenderer.setTexture(m_header.nLayers, pTexture);
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][m_header.nLayers - 1] = 0.0f;
						m_alpha[i][m_header.nLayers - 1] = 0;
					}
					AddTextureLayer(pTexture->GetName());
					mRenderer.textureAdded();
					return m_header.nLayers++;
				}
			default:
				{
					if(m_locked)
						return -1;
					mRenderer.setTexture(1, pTexture);
					m_textureLayers[1].texOffset = m_pParent->AddTexture(pTexture->GetName());
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][0] = 0.0f;
						m_alpha[i][0] = 0;
					}
					return 1;
				}
			}
		}
	case 16 | 4:
		{
			switch(m_header.nLayers)
			{
			case 1:
			case 2:
			case 3:
				{
					mRenderer.setTexture(m_header.nLayers, pTexture);
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][m_header.nLayers - 1] = 0.0f;
						m_alpha[i][m_header.nLayers - 1] = 0;
					}
					i32 ret = m_header.nLayers++;
					AddTextureLayer(pTexture->GetName());
					mRenderer.textureAdded();
					return ret;
				}
			default:
				{
					if(m_locked)
						return -1;
					mRenderer.setTexture(2, pTexture);
					m_textureLayers[2].texOffset = m_pParent->AddTexture(pTexture->GetName());
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][1] = 0.0f;
						m_alpha[i][1] = 0;
					}
					return 2;
				}
			}
		}
	case 16 | 8:
		{
			switch(m_header.nLayers)
			{
			case 1:
			case 2:
			case 3:
				{
					mRenderer.setTexture(m_header.nLayers, pTexture);
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][m_header.nLayers - 1] = 0.0f;
						m_alpha[i][m_header.nLayers - 1] = 0;
					}
					i32 ret = m_header.nLayers++;
					AddTextureLayer(pTexture->GetName());
					mRenderer.textureAdded();
					return ret;
				}
			default:
				{
					if(m_locked)
						return -1;
					mRenderer.setTexture(3, pTexture);
					m_textureLayers[3].texOffset = m_pParent->AddTexture(pTexture->GetName());
					pTexture->AddRef();
					for(ui32 i = 0; i < 4096; ++i)
					{
						m_alphaf[i][2] = 0.0f;
						m_alpha[i][2] = 0;
					}
					return 3;
				}
			}
		}
	}
	return -1;
}

void ADTChunk::ChangeTexture(Vertex3F midPoint, float radius, float amount, ui32 type, Texture* tex, ui32 brushType)
{
	if(!tex)
		return;

	float dist, xdiff, zdiff;

	bool changed = false;

	xdiff = xbase - midPoint.x + CHUNKSIZE / 2;
	zdiff = zbase - midPoint.z + CHUNKSIZE / 2;

	dist = sqrt(xdiff * xdiff + zdiff * zdiff);

	float sqSize = radius;

	if(dist > (radius + MAPCHUNK_RADIUS))
		return;

	i32 texLevel = -1;
	switch(type)
	{
	case 1: // overwrite low layer (wont need any alphashit)
		{
			texLevel = 0;
			break;
		}

	case 16:
		{
			// only if a free layer is there, no overwrite
			if(m_header.nLayers > 3)
				break;
			else
				texLevel = m_header.nLayers;
			break;
		}

	case 2: // the 3 "overwrite"-cases
		{
			if(type == 2 && m_header.nLayers < 2)
				break;
			texLevel = 1;
			break;
		}
	case 4:
		{
			if(type == 4 && m_header.nLayers < 3)
				break;
			texLevel = 2;
			break;
		}
	case 8:
		{
			if(type == 8 && m_header.nLayers < 4)
				break;
			texLevel = 3;
			break;
		}

	case 16 | 1: // overwrite lowest layer if no layer is free, else take free layer
		{
			if(m_header.nLayers > 3)
				texLevel = 0;
			else
				texLevel = m_header.nLayers;
			break;
		}

	case 16 | 2: // overwrite layer 1 if no layer is free, else take free layer
		{
			if(m_header.nLayers < 2)
				break;
			if(m_header.nLayers > 3)
				texLevel = 1;
			else
				texLevel = m_header.nLayers;
			break;
		}

	case 16 | 4: // overwrite layer 2 if no layer is free, else take free layer
		{
			if(m_header.nLayers < 3)
				break;
			if(m_header.nLayers > 3)
				texLevel = 2;
			else
				texLevel = m_header.nLayers;
			break;
		}

	case 16 | 8: // overwrite layer 3 if no layer is free, else take free layer
		{
			if(m_header.nLayers < 4)
				break;
			if(m_header.nLayers > 3)
				texLevel = 3;
			else
				texLevel = m_header.nLayers;
			break;
		}

	default:
		break;
	}

	if(texLevel == 0)
	{
		if(m_texture == tex) // dont waste time when DecRef would decrement to 0 --> delete texture
			return;

		if(m_locked)
			return;

		m_texture->DecRef();
		m_texture = tex;
		tex->AddRef();
		m_textureLayers[0].texOffset = m_pParent->AddTexture(tex->GetName());
		return;
	}

	float change = CHUNKSIZE / 64.0f;

	float zPos = zbase;
	float xPos = xbase;

	amount = sSelection.GetTextureIntensity();

	float am = 1.0f;
	if(amount > 20.0f)
		am = 1.5f;
	else if(amount < 0.0f)
		am = 1.0f;
	else
		am = (1.0f / 40.0f) * amount + 1.0f;
	
	float z = 2.0f * am;
	float p = 0.9f;

	float a = 1 - z - ((-2 * powf(p, 2.0f) + 1) * (z - 1)) / (2 * p * (p - 1));
	float b = ((-2 * powf(p, 2.0f) + 1) * (z - 1)) / (2 * p * (p - 1));

	for(ui32 i = 0; i < 64; ++i)
	{
		xPos = xbase;
		for(ui32 j = 0; j < 64; ++j)
		{
			xdiff = xPos - midPoint.x;
			zdiff = zPos - midPoint.z;
			dist = sqrt(xdiff * xdiff + zdiff * zdiff);

			switch(brushType)
			{
			case 0:
				{
					if(dist > radius)
					{
						xPos += change;
						continue;
					}
					sqSize = radius;
					break;
				}
			case 1:
				{
					if(abs(xdiff) > sqSize || abs(zdiff) > sqSize)
					{
						xPos += change;
						continue;
					}
					break;
				}
			}
			texLevel = AddTexture(texLevel, tex, type);
			if(texLevel == -1)
				continue;

			sD3D.AddChanged(m_pParent);

			float ref = dist / sqSize;
			float pressure = a * powf(ref, 2.0f) + b * ref + z;
			float alOld = m_alphaf[i * 64 + j][texLevel - 1];
			float div = (alOld / 51.0f) + 5.0f;
			float alNew = max(min(alOld + (alOld) * pressure / 2, sSelection.GetTextureGradient() * 255.0f), 1.0f);
			m_alphaf[i * 64 + j][texLevel - 1] = alNew;
			m_alpha[i * 64 + j][texLevel - 1] = (ui8)alNew;
			for(ui32 l = 0; l < 3; ++l)
			{
				if(l == (texLevel - 1))
					continue;
				alOld = m_alphaf[i * 64 + j][l];
				float div = (alOld / 51.0f) + 5.0f;
				float alNew = max(min(alOld - (alOld / (16 * div)) * pressure, 255.0f), 1.0f);
				m_alphaf[i * 64 + j][l] = alNew;
				m_alpha[i * 64 + j][l] = (ui8)alNew;

			}
			xPos += change;
		}
		zPos += change;
	}

	IDirect3DSurface9* pSurf;
	m_alphaTex[0]->GetSurfaceLevel(0, &pSurf);
	RECT r;
	r.top = r.left = 0;
	r.bottom = r.right = 64;
	D3DXLoadSurfaceFromMemory(pSurf, 0, 0, m_alpha, D3DFMT_A8R8G8B8, 64 * 4, 0, &r, D3DX_DEFAULT, 0);
}

void ADTChunk::RecalcOffsets(ui32 pos, i32 change)
{
	if(m_baseOffset + m_header.ofsAlpha >= pos)
		m_header.ofsAlpha += change;
	if(m_baseOffset + m_header.ofsEmitters >= pos)
		m_header.ofsEmitters += change;
	if(m_baseOffset + m_header.ofsHeights >= pos)
		m_header.ofsHeights += change;
	if(m_baseOffset + m_header.ofsLayers >= pos)
		m_header.ofsLayers += change;
	if(m_baseOffset + m_header.ofsLiquid >= pos)
		m_header.ofsLiquid += change;
	if(m_baseOffset + m_header.ofsMCCV >= pos)
		m_header.ofsMCCV += change;
	if(m_baseOffset + m_header.ofsNormals >= pos)
		m_header.ofsNormals += change;
	if(m_baseOffset + m_header.ofsRefs >= pos)
		m_header.ofsRefs += change;
	if(m_baseOffset + m_header.ofsShadow >= pos)
		m_header.ofsShadow += change;
}

void ADTChunk::BlurHeight(Vertex3F midPoint, float radius, float amount, ui32 type)
{
	amount = (-amount / 140.0f) + 1;
	float dist, xdiff, zdiff;

	bool changed = false;

	xdiff = xbase - midPoint.x + CHUNKSIZE / 2;
	zdiff = zbase - midPoint.z + CHUNKSIZE / 2;

	dist = sqrt(xdiff * xdiff + zdiff * zdiff);

	if(dist > (radius + MAPCHUNK_RADIUS))
		return;

	for(ui32 i = 0; i < 145; ++i)
	{
		xdiff = rlVertices[i].x - midPoint.x;
		zdiff = rlVertices[i].z - midPoint.z;

		dist = sqrt(xdiff * xdiff + zdiff * zdiff);
		if(dist < radius)
		{
			float TotalHeight;
			float TotalWeight;
			float tx, tz, h;
			Vertex3F TempVec;
			int Rad = (int)(radius / UNITSIZE);

			TotalHeight = 0;
			TotalWeight=0;
			for(int j = -Rad * 2; j <= Rad * 2; j++)
			{
				tz = midPoint.z + j * UNITSIZE / 2;
				for(int k = -Rad; k <= Rad; k++)
				{
					tx = midPoint.x + k * UNITSIZE + (j % 2) * UNITSIZE / 2.0f;
					xdiff = tx - rlVertices[i].x;
					zdiff = tz - rlVertices[i].z;
					float dist2 = sqrt(xdiff * xdiff + zdiff * zdiff);
					if(dist2 > radius)
						continue;
					float height = rlVertices[i].y;
					sD3D.getLandHeightFast(tx, tz, height);
					TotalHeight += (1.0f - dist2 / radius) * height;
					TotalWeight += (1.0f - dist2 / radius);
				}
			}


			h = TotalHeight / TotalWeight;
			if(type == 0 || type == 4 || type == 3)
			{
				rlVertices[i].y = amount * rlVertices[i].y + (1 - amount) * h;
			}
			else if(type == 1)
			{
				float nremain = 1 - (1 - amount) * (1 - dist / radius);
				rlVertices[i].y = nremain * rlVertices[i].y + (1 - nremain) * h;
			}
			else if(type == 2)
			{
				float nremain = 1.0f - pow(1.0f - amount, (1.0f + dist / radius));
				rlVertices[i].y = nremain * rlVertices[i].y + (1 - nremain) * h;
			}

			changed = true;
		}
	}

	if(changed && isGraphicsLoaded())
	{
		sD3D.AddChanged(m_pParent);
		void* pLock;
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Lock(&pLock);
		memcpy(pLock, rlVertices, 145 * sizeof(Vertex3FNT2));
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Unlock();
	}
}

std::vector<std::string> ADTChunk::GetTextures()
{
	std::vector<std::string> vRet;
	for(ui32 i = 0; i < m_header.nLayers; ++i)
		vRet.push_back(m_pParent->GetTexture(m_textureLayers[i].texOffset));
	return vRet;
}

void ADTChunk::DrawMCCV(ui8 facAlpha, ui8 facRed, ui8 facGreen, ui8 facBlue, Vertex3F point, float radius)
{
	float dist, xdiff, zdiff;

	bool changed = false;

	xdiff = xbase - point.x + CHUNKSIZE / 2;
	zdiff = zbase - point.z + CHUNKSIZE / 2;

	dist = sqrt(xdiff * xdiff + zdiff * zdiff);

	if(dist > (radius + MAPCHUNK_RADIUS))
		return;

	for(ui32 i = 0; i < 145; ++i)
	{
		Vertex3FCNT2& v = rlVertices[i];
		xdiff = v.x - point.x;
		zdiff = v.z - point.z;

		dist = sqrt(xdiff * xdiff + zdiff * zdiff);

		if(dist > radius)
			continue;

		rlVertices[i].dwColor = ui32(facAlpha) << 24 | ui32(facRed) << 16 | ui32(facGreen) << 8 | ui32(facBlue) << 0;
		m_mccv[i] = rlVertices[i].dwColor;

		changed = true;
	}
	
	if(changed)
	{
		if(!m_header.ofsMCCV)
		{
			ui8 dummy[145 * 4 + 8];
			*(ui32*)dummy = 'MCCV';
			*(ui32*)(dummy + 4) = 145 * 4;
			memcpy(dummy + 8, m_mccv, 145 * 4);
			m_parentBuffer->Append(m_baseOffset + sizeof(m_header) + 0x08, 145 * 4 + 8, dummy);
			RecalcOffsets(m_baseOffset + m_header.ofsMCCV, 145 * 4 + 8);
			m_pParent->RecalcOffsets(m_baseOffset + sizeof(m_header) + 0x08, 145 * 4 + 8);
			m_header.ofsMCCV = sizeof(m_header) + 0x08;
			m_chunkSize += 145 * 4 + 8;
			m_pParent->UpdateSizes(145 * 4 + 8, m_baseOffset);
			std::string szFileName = m_pParent->GetFileName();
			std::string s2 = szFileName;
			std::transform(s2.begin(), s2.end(), s2.begin(), tolower);
			std::string::size_type t = szFileName.find("northrend");
			m_header.flags |= 0x40;
			if(t == std::string::npos)
			{
				t = szFileName.rfind('\\');
				std::string::size_type t2 = szFileName.find('_', t);
				std::string sMap = szFileName.substr(t + 1, (t2 - t - 1));
				std::string sWDT = "World\\Maps\\";
				sWDT += sMap + "\\" + sMap + ".wdt";
				CMpqFile fil(sWDT);
				if(!fil.IsOpened())
					return;
				WDTHeader hdr;
				fil.Read(0x14, sizeof(WDTHeader), &hdr);
				if(hdr.flags & 0x02)
					return;
				std::string hud = "Changed ";
				hud += sMap + ".wdt!";
				sHud.SetText(hud, 6000, 0xFF7FFF7F);
				FileBuffer buf(fil);
				hdr.flags |= 0x02;
				buf.Write(0x14, sizeof(WDTHeader), &hdr);
				char curDir[MAX_PATH];
				GetCurrentDirectory(MAX_PATH, curDir);
				CreateDirectory("Save", 0);
				SetCurrentDirectory("./Save");
				CreateDirectory("World", 0);
				SetCurrentDirectory("./World");
				CreateDirectory("Maps", 0);
				SetCurrentDirectory("./Maps");
				CreateDirectory(sMap.c_str(), 0);
				std::string nD = "./";
				nD += sMap;
				SetCurrentDirectory(nD.c_str());
				buf.SaveToFile(sMap + ".wdt");
				SetCurrentDirectory(curDir);
			}
		}
		m_parentBuffer->Write(m_baseOffset + m_header.ofsMCCV + 0x08, 145 * 4, m_mccv);
		sD3D.AddChanged(m_pParent);
		void* pLock;
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Lock(&pLock);
		memcpy(pLock, rlVertices, 145 * Vertex3FCNT2::stride);
		m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Unlock();

	}
}

void ADTChunk::LoadAlpha()
{
	memset(m_alpha, 255, sizeof(m_alpha));
	if(!m_pParent->GetWdtHeader())
	{
		std::string szFileName = m_pParent->GetFileName();
		std::string::size_type t1 = szFileName.rfind('\\');
		std::string::size_type t2 = szFileName.find('_', t1);
		std::string sMap = szFileName.substr(t1 + 1, (t2 - t1 - 1));
		std::string sWDT = "World\\Maps\\";
		sWDT += sMap + "\\" + sMap + ".wdt";
		CMpqFile fil(sWDT);
		WDTHeader* hdr = new WDTHeader();
		fil.Read(0x14, sizeof(WDTHeader), hdr);
		m_pParent->SetWdtHeader(hdr);
	}

	TileTexLayer* t = (TileTexLayer*)m_parentBuffer->GetPointer(m_baseOffset + m_header.ofsLayers + 0x08);
	for(ui32 i = 0; i < m_header.nLayers; ++i)
	{
		m_textureLayers.push_back(t[i]);
	}

	bool m_reflSet = false;
	if(m_header.nLayers > 1)
	{
		for(ui32 i = 1; i < m_header.nLayers; ++i)
		{
			m_blendTexture[i - 1] = CTextureHandler::GetInstance()->GetTexture(m_pParent->GetTexture(t[i].texOffset));
			m_blendTexture[i - 1]->SetADTTexture();
			if(t[i].flags & 0x200)
			{
				LoadAlphaSelfCompress(i - 1, t[i].ofsAlpha);
				continue;
			}
			if(m_pParent->GetWdtHeader()->flags & 0x4)
			{
				LoadAlphaUncompress(i - 1, t[i].ofsAlpha);
				continue;
			}

			ui8* alphaPtr = m_parentBuffer->GetPointer(m_baseOffset + 0x8 + m_header.ofsAlpha + t[i].ofsAlpha);
			ui32 bufferPtr = 0;
			ui32 mapPtr = 0;
			for (int j = 0; j < 64; j++) 
			{
				for (int k = 0; k < 32; k++) 
				{
					if(k != 31)
					{
						m_alphaf[mapPtr][i - 1] = m_alpha[mapPtr++][i - 1] = (ui8)(((255 * ((i32)(alphaPtr[bufferPtr] & 0x0f))) / 0x0f));
						m_alphaf[mapPtr][i - 1] = m_alpha[mapPtr++][i - 1] = (ui8)(((255 * ((i32)(alphaPtr[bufferPtr++] & 0xf0))) / 0xf0));
					}
					else
					{
						m_alphaf[mapPtr][i - 1] = m_alpha[mapPtr++][i - 1] = (ui8)(((255 * ((i32)(alphaPtr[bufferPtr] & 0x0f))) / 0x0f));
						m_alphaf[mapPtr][i - 1] = m_alpha[mapPtr++][i - 1] = (ui8)(((255 * ((i32)(alphaPtr[bufferPtr++] & 0x0f))) / 0x0f));
					}
				}
			}
		}
	}
	HRESULT hRes = D3DXCreateTexture(sD3D, 64, 64, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, m_alphaTex);
	if(FAILED(hRes))
	{
		Log::Print("Create failed: %X\n", HRESULT_CODE(hRes));
	}
	IDirect3DSurface9* pSurf;
	m_alphaTex[0]->GetSurfaceLevel(0, &pSurf);
	RECT r;
	r.top = r.left = 0;
	r.bottom = r.right = 64;
	hRes = D3DXLoadSurfaceFromMemory(pSurf, 0, 0, m_alpha, D3DFMT_A8R8G8B8, 64 * 4, 0, &r, D3DX_DEFAULT, 0);
}

void ADTChunk::LoadAlphaSelfCompress(ui32 index, ui32 offset)
{
	ui32 counterIn = 0;
	ui32 counterOut = 0;
	ui8* ptr = m_parentBuffer->GetPointer(m_baseOffset + 0x8 + m_header.ofsAlpha + offset);
	while(counterOut < 4096)
	{
		bool fill = (ptr[counterIn] & 0x80) != 0;
		ui32 n = ptr[counterIn] & 0x7F;
		counterIn++;
		for(ui32 k = 0; k < n; k++)
		{
			m_alphaf[counterOut][index] = m_alpha[counterOut][index] = ptr[counterIn];
			m_layerSizeCompr[index]++;
			counterOut++;
			if(!fill)
				counterIn++;
		}
		if(fill) 
			counterIn++;
	}
	m_layerSizeCompr[index] = counterIn - 1;
	//CreateUncompressedAlpha(index);
}

void ADTChunk::LoadAlphaUncompress(ui32 index, ui32 offset)
{
	ui8* ptr = m_parentBuffer->GetPointer(m_baseOffset + 0x8 + m_header.ofsAlpha + offset);
	for(ui32 i = 0; i < 4096; ++i)
		m_alphaf[i][index] = m_alpha[i][index] = ptr[i];
}


// <-- Outdated, succeeded by NormalizeAlphas -->
void ADTChunk::CreateUncompressedAlpha(ui32 alphaLayer)
{

}

void ADTChunk::NormalizeAlphas()
{
	if(m_header.nLayers == 1)
		return;
	ui8* pAlpha = new ui8[(m_header.nLayers - 1) * 4096];
	for(ui32 i = 0; i < (m_header.nLayers - 1); ++i)
	{
		for(ui32 j = 0; j < 4096; ++j)
		{
			pAlpha[i * 4096 + j] = m_alpha[j][i];
		}
		if(m_textureLayers[i + 1].flags & 0x200)
			m_textureLayers[i + 1].flags &= ~ui32(0x200);
		m_textureLayers[i + 1].ofsAlpha = i * 4096;
	}

	ui32 alphaSize = m_header.sizeAlpha - 8;
	i32 diff = ((m_header.nLayers - 1) * 4096) - alphaSize;
	if(diff <= 0) // either error (hopefully not) or just every layer !(flags & 0x200)
	{
		delete [] pAlpha;
		return;
	}

	ui32 sizeOld = alphaSize;
	alphaSize = (m_header.nLayers - 1) * 4096;
	ui8* dummy = new ui8[diff];
	m_header.sizeAlpha = alphaSize + 8;
	m_parentBuffer->Write(m_baseOffset + m_header.ofsAlpha + 0x04, 4, &alphaSize);
	m_parentBuffer->Append(m_baseOffset + m_header.ofsAlpha + 0x08 + sizeOld, diff, dummy);
	RecalcOffsets(m_baseOffset + m_header.ofsAlpha + 0x08 + sizeOld, diff);
	m_pParent->RecalcOffsets(m_baseOffset + m_header.ofsAlpha + 0x08 + sizeOld, diff);
	m_pParent->UpdateSizes(diff, m_baseOffset);
	m_parentBuffer->Write(m_baseOffset + m_header.ofsAlpha + 0x08, alphaSize, pAlpha);
	m_chunkSize += diff;
	delete [] pAlpha;
	delete [] dummy;
}

void ADTChunk::LoadShadows()
{
	memset(m_shadowMap, 0x00, 4096 * 4);

	if(m_header.flags & 0x1)
	{
		ui32 basePos = m_baseOffset + m_header.ofsShadow + 0x04;
		ui32 sizeShadow;
		m_parentBuffer->Read(basePos, 4, &sizeShadow);
		basePos += 4;
		if(sizeShadow >= 512)
		{
			ui8 c[8];
			ui32 counter = 0;
			for(ui32 i = 0; i < 64; ++i)
			{
				m_parentBuffer->Read(basePos + i * 8, 8, c);
				for(ui32 j = 0; j < 8; ++j)
				{
					for(ui32 b = 0x01; b < 0x100; b <<= 1)
					{
						m_shadowMap[counter++][3] = (c[j] & b) ? 85 : 0;
					}
				}
			}
		}
	}

	HRESULT hRes = D3DXCreateTexture(sD3D, 64, 64, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &m_shadowTex);
	IDirect3DSurface9* pSurf;
	m_shadowTex->GetSurfaceLevel(0, &pSurf);
	RECT r;
	r.top = r.left = 0;
	r.bottom = r.right = 64;
	hRes = D3DXLoadSurfaceFromMemory(pSurf, 0, 0, m_shadowMap, D3DFMT_A8R8G8B8, 64 * 4, 0, &r, D3DX_DEFAULT, 0);
}

void ADTChunk::LoadHoles()
{
	ui8 holeTexture[4096][4];
	memset(holeTexture, 0xFF, 4 * 4096);
	float step = CHUNKSIZE / 64;
	ui32 counter = 0;
	for(ui32 j = 0; j < 64; ++j)
	{
		for(ui32 i = 0; i < 32; ++i)
		{
			float x = i * step * 2;
			float y = j * step;
			ui32 quadX = floor(x / HOLELEN);
			ui32 quadY = floor(y / HOLELEN);
			bool factor1 = true, factor2 = true;
			if(quadX < 4 && quadY < 4)
			{
				factor1 = (m_header.holes & holeOffsets[quadY][quadX]) == 0;
			}
			x += step;
			quadX = floor(x / HOLELEN);
			if(quadX < 4)
			{
				factor2 = (m_header.holes & holeOffsets[quadY][quadX]) == 0;
			}
			holeTexture[counter++][3] = factor1 ? 255 : 0;
			holeTexture[counter++][3] = factor2 ? 255 : 0;
		}
	}
	m_renderLock.Acquire();
	while(m_holeTexture && m_holeTexture->Release());
	HRESULT hRes = D3DXCreateTexture(sD3D, 64, 64, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &m_holeTexture);
	if(FAILED(hRes))
	{
		Log::Print("Failed to create holetexture. Reason: %u\n", HRESULT_CODE(hRes));
		m_renderLock.Release();
		return;
	}
	IDirect3DSurface9* pSurfa;
	m_holeTexture->GetSurfaceLevel(0, &pSurfa);
	RECT r;
	r.top = r.left = 0;
	r.bottom = r.right = 64;
	hRes = D3DXLoadSurfaceFromMemory(pSurfa, 0, 0, holeTexture, D3DFMT_A8R8G8B8, 64 * 4, 0, &r, D3DX_DEFAULT, 0);
	m_renderLock.Release();
}

void ADTChunk::ToggleHole(Vertex3F pos)
{
	if(pos.z < xbase || pos.z > xbase + CHUNKSIZE)
		return;
	if(pos.x < zbase || pos.x > zbase + CHUNKSIZE)
		return;
	
	pos.z -= xbase;
	pos.x -= zbase;

	ui32 quadX = (pos.z / HOLELEN);
	ui32 quadZ = (pos.x / HOLELEN);

	if(quadX < 4 || quadZ < 4)
	{
		m_header.holes ^= holeOffsets[quadZ][quadX];
		// before reloading the holes because maybe there will be an error at loading but we still could save
		sD3D.AddChanged(m_pParent);
		LoadHoles();
	}
}

void ADTChunk::DrawShadow(Vertex3F pos, float radius, bool on)
{
	float dist, xdiff, zdiff;

	bool changed = false;

	xdiff = xbase - pos.z + CHUNKSIZE / 2;
	zdiff = zbase - pos.x+ CHUNKSIZE / 2;

	dist = sqrt(xdiff * xdiff + zdiff * zdiff);

	if(dist > (radius + MAPCHUNK_RADIUS))
		return;

	float change = CHUNKSIZE / 64.0f;
	float zPos = zbase;
	float xPos;

	for(ui32 j = 0; j < 64; j++)
	{
		xPos = xbase;
		for(ui32 i = 0; i < 64; i++)
		{
			xdiff = xPos - pos.z;
			zdiff = zPos - pos.x;
			dist = abs(sqrt(xdiff * xdiff + zdiff * zdiff));
			
			if(dist > radius)
			{
				xPos += change;
				continue;
			}

			xPos += change;
			changed = true;
			m_shadowMap[j * 64 + i][3] = on ? 85 : 0;
		}
		zPos += change;
	}
	if(changed)
	{
		if(!(m_header.flags & 0x01))
		{
			m_header.flags |= 0x01;
			ui8 dummy[520] = { 0 };
			*(ui32*)dummy = 'MCSH';
			*(ui32*)(dummy + 4) = 512;
			m_parentBuffer->Append(m_baseOffset + 0x08 + sizeof(m_header), 520, dummy);
			RecalcOffsets(m_baseOffset + 0x08 + sizeof(m_header), 520);
			m_pParent->RecalcOffsets(m_baseOffset + 0x08 + sizeof(m_header), 520);
			m_header.ofsShadow = sizeof(m_header) + 0x08;
			m_header.sizeShadow = 512;
			m_chunkSize += 520;
			m_pParent->UpdateSizes(520, m_baseOffset);
		}
		sD3D.AddChanged(m_pParent);
		ReloadShadows();
	}
}

void ADTChunk::ReloadShadows()
{
	m_renderLock.Acquire();
	while(m_shadowTex && m_shadowTex->Release());
	HRESULT hRes = D3DXCreateTexture(sD3D, 64, 64, 1, 0, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, &m_shadowTex);
	IDirect3DSurface9* pSurf;
	m_shadowTex->GetSurfaceLevel(0, &pSurf);
	RECT r;
	r.top = r.left = 0;
	r.bottom = r.right = 64;
	hRes = D3DXLoadSurfaceFromMemory(pSurf, 0, 0, m_shadowMap, D3DFMT_A8R8G8B8, 64 * 4, 0, &r, D3DX_DEFAULT, 0);
	m_renderLock.Release();
}

void ADTChunk::Load()
{
	ui32 alSize = 0;
	m_parentBuffer->Read(m_baseOffset + m_header.ofsAlpha + 0x4, 4, &alSize);
	TileTexLayer* tl = (TileTexLayer*)m_parentBuffer->GetPointer(m_baseOffset + 0x8 + m_header.ofsLayers);
	m_texture = CTextureHandler::GetInstance()->GetTexture(m_pParent->GetTexture(tl->texOffset));
	if(!m_texture)
		m_texture = CTextureHandler::GetInstance()->GetTexture("Tileset\\Generic\\Grey.blp");

	m_texture->SetADTTexture();
	for(ui32 i = 0; i < 3; ++i)
		m_alphaTex[i] = 0;
	LoadAlpha();
	LoadShadows();
	LoadHoles();
	LoadMCCV();
	LoadNormals();
	LoadVertices();
	ui32* references = (ui32*)m_parentBuffer->GetPointer(m_baseOffset + m_header.ofsRefs + 0x8);
	for(ui32 i = 0; i < m_header.nDoodadrefs; ++i) {
		m_objectRefs.insert(references[i]);
		m_pParent->loadDoodad(references[i]);
	}
	m_loaded = true;
	m_pParent->getSyncLoader().setChunkDirty(m_header.indexX + m_header.indexY * 16);
}

void ADTChunk::InitStatics()
{
	gLockTex = CTextureHandler::GetInstance()->GetTexture("Interface\\LFGFrame\\UI-LFG-ICON-LOCK.blp");
	ui32 counter = 0;
	float tx, ty;
	for(int j = 0; j < 17; j++) 
	{
		for(int i = 0; i < ((j % 2) ? 8 : 9); i++) 
		{
			tx = (float)i;
			ty = (float)j * 0.5f;
			if (j % 2)
				tx += 0.5;
			m_texcoords[counter][0] = tx;
			m_texcoords[counter++][1] = ty;
		}
	}
	counter = 0;
	const float alpha_half = 0.5f * (62.0f/64.0f) / 8.0f;
	for(int j = 0; j < 17; j++) 
	{
		for(int i = 0; i < ((j % 2) ? 8 : 9); i++) 
		{
			tx = (62.0f / 64.0f) / 8.0f * i;
			ty = (62.0f / 64.0f) / 8.0f * j * 0.5f;
			if (j % 2)
				tx += alpha_half;
			m_alphaCoords[counter][0] = tx;
			m_alphaCoords[counter++][1] = ty;
		}
	}
	for(ui32 i = 0; i < 8; ++i)
	{
		for(ui32 j = 0; j < 8; ++j)
		{
			ui32 topLeft = i * 17 + j;
			ui32 midPoint = i * 17 + j + 9;
			ui32 topRight = i * 17 + j + 1;
			ui32 bottomRight = i * 17 + j + 18;
			ui32 bottomLeft = (i + 1) * 17 + j;
			triangleIndices[i * 8 + j][0] = topLeft;
			triangleIndices[i * 8 + j][1] = midPoint;
			triangleIndices[i * 8 + j][2] = bottomLeft;
			triangleIndices[i * 8 + j][3] = topLeft;
			triangleIndices[i * 8 + j][4] = midPoint;
			triangleIndices[i * 8 + j][5] = topRight;
			triangleIndices[i * 8 + j][6] = topRight;
			triangleIndices[i * 8 + j][7] = midPoint;
			triangleIndices[i * 8 + j][8] = bottomRight;
			triangleIndices[i * 8 + j][9] = bottomRight;
			triangleIndices[i * 8 + j][10] = midPoint;
			triangleIndices[i * 8 + j][11] = bottomLeft;
		}
	}
	m_initEffects = false;
}

void ADTChunk::LoadNormals()
{
	i8* ptr = (i8*)m_parentBuffer->GetPointer(m_baseOffset + m_header.ofsNormals + 0x8);
	ui32 counter = 0;
	for(ui32 i = 0; i < 17; ++i)
	{
		for(ui32 j = 0; j < ((i % 2) ? 8u : 9u); ++j)
		{
			Vertex3F nor;
			nor.x = -((float)(*ptr++) / 127.0f);
			nor.z = -((float)(*ptr++) / 127.0f);
			nor.y = (float)(*ptr++) / 127.0f;
			m_normals[counter++] = nor;
		}
	}
}

void ADTChunk::LoadVertices()
{
	float* pHeights = (float*)m_parentBuffer->GetPointer(m_baseOffset + m_header.ofsHeights + 0x8);

	mPosMin.y = 99999.999f;
	mPosMax.y = -99999.999f;
	mPosMin.x = xbase;
	mPosMin.z = zbase;
	mPosMax.x = mPosMin.x + CHUNKSIZE;
	mPosMax.z = mPosMin.z + CHUNKSIZE;
	ui32 counter = 0;
	for(ui32 i = 0; i < 17; ++i)
	{
		for(ui32 j = 0; j < ((i % 2) ? 8u : 9u); ++j)
		{
			float y, x, z;
			y = pHeights[counter];
			x = j * UNITSIZE;
			z = i * UNITSIZE * 0.5f;
			if(i % 2)
				x += 0.5f * UNITSIZE;
			Vertex3FCNT2 vert;
			vert.x = xbase + x;
			vert.y = ybase + y;
			vert.z = zbase + z;
			vert.u = m_texcoords[counter][0];
			vert.v = m_texcoords[counter][1];
			vert.s = m_alphaCoords[counter][0];
			vert.t = m_alphaCoords[counter][1];
			vert.nx = m_normals[counter].x;
			vert.ny = m_normals[counter].y;
			vert.nz = m_normals[counter].z;
			vert.dwColor = m_mccv[counter];
			rlVertices[counter++] = vert;

			if(vert.y < mPosMin.y)
				mPosMin.y = vert.y;
			if(vert.y > mPosMax.y)
				mPosMax.y = vert.y;
		}
	}
	//m_mesh.Load(rlVertices, (VertexFMT)Vertex3FCNT2::FVF, 145, 64 * 4, (ui16*)triangleIndices);
	m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).setVertices(rlVertices);
}

void ADTChunk::LoadMCCV()
{
	memset(m_mccv, 0x7F, 145 * 4);
	if(m_header.ofsMCCV != 0)
	{
		m_parentBuffer->Read(m_baseOffset + m_header.ofsMCCV + 0x08, 145 * 4, m_mccv);
	}
}

void ADTChunk::BeforeReset()
{
	return;
	if(!m_loaded)
		return;

	for(ui32 i = 0; i < 3; ++i)
		if(m_alphaTex[i])
		{
			while(m_alphaTex[i]->Release());
			m_alphaTex[i] = 0;
		}

	while(m_shadowTex && m_shadowTex->Release());
	while(m_holeTexture && m_holeTexture->Release());
	m_shadowTex = 0;
	m_holeTexture = 0;
	m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().BeforeReset();
	for(ui32 i = 0; i < 4; ++i)
	{
		if(m_blendEffect[i])
		{
			while(m_blendEffect[i]->Release());
			m_blendEffect[i] = 0;
		}
	}
}

void ADTChunk::OnReset()
{
	return;
	if(!m_loaded)
		return;

	LoadHoles();
	ReloadShadows();
	HRESULT hRes = D3DXCreateTexture(sD3D, 64, 64, 1, 0, D3DFMT_R8G8B8, D3DPOOL_MANAGED, m_alphaTex);
	IDirect3DSurface9* pSurf;
	m_alphaTex[0]->GetSurfaceLevel(0, &pSurf);
	RECT r;
	r.top = r.left = 0;
	r.bottom = r.right = 64;
	D3DXLoadSurfaceFromMemory(pSurf, 0, 0, m_alpha, D3DFMT_R8G8B8, 64 * 3, 0, &r, D3DX_DEFAULT, 0);
	m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Load(rlVertices, (VertexFMT)Vertex3FCNT2::FVF, 145, 64 * 4, (ui16*)triangleIndices); 
	if(m_blendEffect[0] == 0)
	{
		m_blendEffect[0] = sShaderMgr.GetShader("VS_1.fx");
		m_blendEffect[1] = sShaderMgr.GetShader("VS_2.fx");
		m_blendEffect[2] = sShaderMgr.GetShader("VS_3.fx");
		m_blendEffect[3] = sShaderMgr.GetShader("VS_4.fx");
	}
}

void ADTChunk::UpdateMDXPosition(ADTModel* pBaseMap, ui32 doodad, float x, float y, float z)
{
	/*if(!m_loaded)
		return;

	M2Model* pModel = pBaseMap->GetDoodad(doodad);
	if(!pModel)
		return;
		
	const M2FileHeader& hdr = pModel->GetHeader();

	if((z + hdr.boundingRadius) >= xbase && (z - hdr.boundingRadius) <= xbase + CHUNKSIZE && (x + hdr.boundingRadius) >= zbase && (x - hdr.boundingRadius) <= zbase + CHUNKSIZE)
	{
		if(m_objectRefs.find(doodad) != m_objectRefs.end())
			return;





	}*/
}

float ADTChunk::GetLandHeight(float x, float z)
{
	FLOAT dist = 0;
	if(!m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Intersect(D3DXVECTOR3(x, mPosMax.y + 5, z), D3DXVECTOR3(0, -1, 0), dist))
		return 65536.0f;

	return mPosMax.y - dist + 5;
}

void ADTChunk::ReloadNormals()
{
	for(ui32 i = 0; i < 145; ++i)
	{
		D3DXVECTOR3 N1, N2, N3, N4;
		D3DXVECTOR3 P1, P2, P3, P4;

		P1.x = rlVertices[i].x - UNITSIZE * 0.5f;
		P1.z = rlVertices[i].z - UNITSIZE * 0.5f;
		P1.y = rlVertices[i].y; //sD3D.GetLandHeight(P1.x, P1.z);
		sD3D.getLandHeightFast(P1.x, P1.z, P1.y);

		P2.x = rlVertices[i].x + UNITSIZE * 0.5f;
		P2.z = rlVertices[i].z - UNITSIZE * 0.5f;
		P2.y = rlVertices[i].y; //P2.y = sD3D.GetLandHeight(P2.x, P2.z);
		sD3D.getLandHeightFast(P2.x, P2.z, P2.y);

		P3.x = rlVertices[i].x + UNITSIZE * 0.5f;
		P3.z = rlVertices[i].z + UNITSIZE * 0.5f;
		P3.y = rlVertices[i].y; //P3.y = sD3D.GetLandHeight(P3.x, P3.z);
		sD3D.getLandHeightFast(P3.x, P3.z, P3.y);

		P4.x = rlVertices[i].x - UNITSIZE * 0.5f;
		P4.z = rlVertices[i].z + UNITSIZE * 0.5f;
		P4.y = rlVertices[i].y; //P4.y = sD3D.GetLandHeight(P4.x, P4.z);
		sD3D.getLandHeightFast(P4.x, P4.z, P4.y);

		D3DXVECTOR3 vert(rlVertices[i].x, rlVertices[i].y, rlVertices[i].z);

		D3DXVec3Cross(&N1, &(P2 - vert), &(P1 - vert));
		D3DXVec3Cross(&N2, &(P3 - vert), &(P2 - vert));
		D3DXVec3Cross(&N3, &(P4 - vert), &(P3 - vert));
		D3DXVec3Cross(&N4, &(P1 - vert), &(P4 - vert));

		D3DXVECTOR3 Norm = N1 + N2 + N3 + N4;
		D3DXVec3Normalize(&Norm, &Norm);

		Vertex3F n = Vertex3F(Norm.x, Norm.y, Norm.z);
		m_normals[i] = n;
		rlVertices[i].nx = n.x;
		rlVertices[i].ny = n.y;
		rlVertices[i].nz = n.z;
	}
}

void ADTChunk::Update(ui32 diff) {
}

bool ADTChunk::Intersect(D3DXVECTOR3& rayO, D3DXVECTOR3& rayDir, float* pOut) {
	if(!m_loaded)
		return false;

	Vertex3F position = sCamera.GetCamPosition();
	Vertex3F tar = sCamera.GetTargetPosition();
	float distance = sqrtf(powf(xbase - position.x, 2.0f) + powf(zbase - position.z, 2.0f));
	Vertex3F front(tar.x - position.x, tar.y - position.y, tar.z - position.z);
	Vertex3F target((xbase + CHUNKSIZE / 2) - position.x, ybase - position.y, (zbase + CHUNKSIZE / 2) - position.z);
	Vertex3F target2D((xbase + CHUNKSIZE / 2) - position.x, 0.0f, (zbase + CHUNKSIZE / 2) - position.z);

	float angle = acos((front.x * target.x + front.z * target.z + front.y * target.y) / (front.Length() * target.Length()));

	if((angle * 180) / M_PI > 60 && target2D.Length() > 3 * CHUNKSIZE || distance > (fogDistance + 0.2f * TILESIZE))
		return false;

	return m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).getMesh().Intersect(rayO, rayDir, *pOut);
}

bool ADTChunk::isGraphicsLoaded() const {
	return m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16).isFinished();	
}

bool ADTChunk::getLandHeightFast(float x, float z, float& h) {
	if(xbase + CHUNKSIZE < x || zbase + CHUNKSIZE < z)
		return false;

	if(xbase > x || zbase > z)
		return false;

	float dx = x - xbase;
	float dz = z - zbase;

	int row = (i32)(dz / (UNITSIZE * 0.5f) + 0.5f);
	i32 col = (i32)((dx - UNITSIZE * 0.5f * (row % 2)) / UNITSIZE + 0.5f);
	if(row < 0 || col < 0 || row > 16 || col > ((row % 2) ? 8 : 9))
		return false;

	h = rlVertices[17 * (row / 2) + ((row % 2) ? 9 : 0) + col].y;
	return true;
}

ADTChunkLoader& ADTChunk::getChunkLoader() {
	return m_pParent->getSyncLoader().getChunk(m_header.indexX + m_header.indexY * 16);
}